package py.com.alfa.escriba.web.services.impl;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import freemarker.cache.StringTemplateLoader;
import freemarker.core.ParseException;
import freemarker.template.Configuration;
import freemarker.template.MalformedTemplateNameException;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.TemplateNotFoundException;
import py.com.alfa.escriba.exception.ErrorCode;
import py.com.alfa.escriba.exception.ServiceException;
import py.com.alfa.escriba.persistence.dao.EscrituraDAO;
import py.com.alfa.escriba.persistence.dto.TemplatesDTO;
import py.com.alfa.escriba.persistence.dto.VarObjDTO;
import py.com.alfa.escriba.persistence.dto.VariablesDTO;
import py.com.alfa.escriba.persistence.dto.marker.ControlMarkerDTO;
import py.com.alfa.escriba.util.HelperMarker;
import py.com.alfa.escriba.web.services.GenerateEscrituraService;

public class GenerateEscrituraServiceImpl implements GenerateEscrituraService {
	
	private static final String VARIABLE = "\\{\\{([^}]+)\\}\\}";
	private Logger log = LoggerFactory.getLogger(getClass());
	private Pattern pattern = null;
	private EscrituraDAO escritura;
	private HashMap<String, VarObjDTO> variables;
	private List<VariablesDTO> vars;
	private ControlMarkerDTO ctrl = null;
	private int item = 1;
	private Configuration cfg;
   
	
	public GenerateEscrituraServiceImpl() {
		this.pattern = Pattern.compile(VARIABLE);
		this.variables = new HashMap<String, VarObjDTO>();
		this.cfg = new Configuration(Configuration.VERSION_2_3_22);
		this.cfg.setDefaultEncoding("UTF-8");
		this.cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); 
		//this.cfg.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
	}

	public EscrituraDAO getEscritura() {
		return escritura;
	}



	public void setEscritura(EscrituraDAO escritura) {
		this.escritura = escritura;
	}



	@Override
	public String convert(Long idCtrl, Long idTemplate) throws ServiceException {
		this.vars = null;
		String res = null;
		
		log.debug("========= escritura =============");
		if ( idTemplate == null) {
			throw new ServiceException(ErrorCode.VALIDATION_FAILED, "generador.escritura.not.info.template");
		}
		if ( idCtrl == null) {
			throw new ServiceException(ErrorCode.VALIDATION_FAILED, "generador.escritura.not.info.control");
		}		
		TemplatesDTO dto;
		// REcupero el template a procesar
		dto = this.escritura.getTemplatesById(idTemplate);
		if ( dto == null) {
			throw new ServiceException(ErrorCode.CODE_NOT_EXIST, "generador.escritura.not.exist", idTemplate);
		}
		res = dto.getTexto();
		res = this.replaceVar(this.analiza(res, false, null));
		this.createTxt(res);
		res = this.convertMarker(res, idCtrl);
		log.debug(res);
		log.debug("========= FIN escritura =============");
		return res;
	}
  
	private String convertMarker(String str, Long idCtrl) throws ServiceException  {
		   String resul = null;
		   HelperMarker help = new HelperMarker();
		   HashMap<String, Object> dataModel = new HashMap<String, Object>();
		   String name = "escritura";
		   StringWriter out = new StringWriter();
		   StringTemplateLoader sLoader = new StringTemplateLoader();
		   sLoader.putTemplate(name, str);
		   this.cfg.setTemplateLoader(sLoader);
		   Template temp;
			try {
			   temp = this.cfg.getTemplate(name);
			   dataModel.put("control", this.getCtrl(idCtrl));
			   dataModel.put("ListaVacia", new ArrayList<String>());
			   dataModel.put("helper", help);
			   temp.process(dataModel, out);
			   resul = out.toString();
			} catch (TemplateNotFoundException e) {
				log.error(e.getMessage(), e);
				throw new ServiceException(ErrorCode.VALIDATION_FAILED, e.getMessage());
			} catch (MalformedTemplateNameException e) {
				log.error(e.getMessage(), e);
				throw new ServiceException(ErrorCode.VALIDATION_FAILED, e.getMessage());
			} catch (ParseException e) {
				log.error(e.getMessage(), e);
				throw new ServiceException(ErrorCode.VALIDATION_FAILED, e.getMessage());
			} catch (IOException e) {
				log.error(e.getMessage(), e);
				throw new ServiceException(ErrorCode.VALIDATION_FAILED, e.getMessage());
			} catch (TemplateException e) {
				log.error(e.getMessage(), e);
				throw new ServiceException(ErrorCode.VALIDATION_FAILED, e.getMessage());
			}

		   return resul;
		}
		
		private String analiza(String text, boolean repetitivo, String var) throws ServiceException {
			if ( this.pattern == null ) {
				throw new ServiceException(ErrorCode.VALIDATION_FAILED, "generador.escritura.no.patron");  
			}
			
			Matcher m = this.pattern.matcher(text);
			VarObjDTO res = null;
		    while (m.find()) {
		    	res = this.addVariable(m.group(1), repetitivo, var);
		    	if ( res.isTemplate()) {	
		    	   String itemVar = null;
		    	   String format = "%s";
		    	   if ( res.isRepetitivo() ) {
		    	     itemVar = String.format("item%d", this.item++);
		    	     format = "<#list ("+res.getObjecto()+")!ListaVacia as item >%s</#list>";	    	     
		    	   }
		    	   res.setValue(String.format(format,this.analiza(res.getValue(), res.isRepetitivo(), itemVar)));
		    	   text = text.replace(res.getOriginal(), res.getValue());
		    	} 
		    }
		    return text;
			
		}
		
		private String replaceVar(String text) throws ServiceException {
			for (VarObjDTO dto : variables.values()) {
				if ( !dto.isTemplate()) {
					text = text.replace(dto.getOriginal(),dto.getValue());
				}
			}
		    return this.replaceForHtml(text);
			
		}
		
		private VarObjDTO addVariable(String campo, boolean repetitivo, String varItem) throws ServiceException {
		  if ( campo == null ){
			  throw new ServiceException(ErrorCode.VALIDATION_FAILED, "generador.escritura.nulo.campo");
		  }
		  campo = campo.trim();
		  if ( "".equals(campo)) {
			  throw new ServiceException(ErrorCode.VALIDATION_FAILED, "generador.escritura.nulo.campo");
		  }
		  
		  if ( this.variables.containsKey(campo)) {
			  return this.variables.get(campo);
		  }
		 
		  VarObjDTO var = new VarObjDTO();
		  var.setName(campo);  
		  this.buscarVar(var);
		  var.setOriginal(String.format("{{%s}}", campo));
		  this.variables.put(var.getName(), var);
		  return var;
		}
		
		
		private void buscarVar(VarObjDTO var) throws ServiceException {
			List<VariablesDTO> lista = this.getVars();
			Collection<VariablesDTO> result = CollectionUtils.select(lista, new PredicateVar(var.getName()));		
			if ( result == null || result.isEmpty()) {
				throw new ServiceException(ErrorCode.NO_DATA_FOUND, "generador.escritura.no.variables", var.getName());  
			}
			if ( result.size() != 1) {
				throw new ServiceException(ErrorCode.NO_DATA_FOUND, "generador.escritura.no.variables", var.getName());  
			}
			VariablesDTO dto = result.iterator().next();
			var.setTemplate(dto.getIstemplate());
			if ( !dto.getIstemplate()){
				this.setVarContainer(var, dto);
				return;
			}
			var.setObjecto(dto.getCampo());
			var.setRepetitivo(dto.getRepetitivo());
			if ( var.isRepetitivo() && ( var.getObjecto() == null || "".equals(var.getObjecto()))  ){
				throw new ServiceException(ErrorCode.VALIDATION_FAILED, "generador.escritura.no.repetitiva", var.getName());
			}
			
			TemplatesDTO temp = null;
			temp = this.escritura.getTemplatesById(dto.getIdtemplate());
			if ( temp == null) {
				throw new ServiceException(ErrorCode.VALIDATION_FAILED, "generador.escritura.no.template", dto.getIdtemplate(), var.getName());
			}
			var.setValue(temp.getTexto());			

		}
			
		// Tipos C es para condiciones donde debe preguntar si es nulo
		// L es para las listas donde no hay condicion
		// O es del obligatorio van a aparecer *** si no se informo
		// F es opcional aparece ..... si no se informo
		
		private void setVarContainer(VarObjDTO var, VariablesDTO dto) {
			StringBuffer buf = new StringBuffer();		
			if ( "L".equals(dto.getObligatoriedad())) {
				buf.append("(").append(dto.getCampo()).append(")!ListaVacia");					
			} else if ( "C".equals(dto.getObligatoriedad())) {
				buf.append("(").append(dto.getCampo()).append(")?? && ").append(dto.getCampo());					
			} else if ( "O".equals(dto.getObligatoriedad()) || "F".equals(dto.getObligatoriedad())){
				buf.append("<#if (").append(dto.getCampo()).append(")??>");
				buf.append(String.format("${%s}", dto.getCampo())).append("<#else>");
				if ( "O".equals(dto.getObligatoriedad())) {
					buf.append("*****************</#if>");
				} else {
				  buf.append("...................</#if>");
				}
			} else {
				buf.append("<#if (").append(dto.getCampo()).append(")??>");
				buf.append(String.format("${%s}", dto.getCampo())).append("<#else>");
				buf.append("</#if>");			
			}
			var.setValue(buf.toString());
		}

		private List<VariablesDTO> getVars() throws ServiceException {
			if ( this.vars == null) {
				this.vars = this.escritura.listVariables();
			}
			return this.vars;
		}

		private ControlMarkerDTO getCtrl(Long idCtrl) throws ServiceException {
			this.ctrl = this.escritura.getControlesById(idCtrl);
			if ( this.ctrl == null) {
				throw new ServiceException(ErrorCode.NO_DATA_FOUND , "controles.not.exist", idCtrl);
			}
			this.ctrl.setPersonas(this.escritura.getPersonasByCtrl(idCtrl));	
			this.ctrl.setVehiculos(this.escritura.getVehiculosByCtrl(idCtrl));
			this.ctrl.setEscritura(this.escritura.getEscrituraByCtrl(idCtrl));
			this.ctrl.setFormularios(this.escritura.getFormularioByCtrl(idCtrl));
			this.ctrl.setNaturaleza(this.escritura.getNaturalezaByCtrl(idCtrl));
			return this.ctrl;
		}	
		
		private String replaceForHtml(String data) {
			data = data.replace('\n', ' ');
			data = data.replace("á", "&aacute;");
			data = data.replace("Á", "&Aacute;");
			data = data.replace("é", "&eacute;");
			data = data.replace("É", "&Eacute;");
			data = data.replace("í", "&iacute;");
			data = data.replace("Í", "&Iacute;");
			data = data.replace("ó", "&oacute;");
			data = data.replace("Ó", "&Oacute;");		
			data = data.replace("ú", "&uacute;");
			data = data.replace("Ú", "&Uacute;");
			data = data.replace("ñ", "&ntilde");
			data = data.replace("Ñ", "&Ntilde");
			data = data.replace("º", "&deg;");
			return data;
		}
		
		public void createWorld(String data) {
			try {
	            data = String.format("<html><body>%s</body></html>", data);
	            PrintWriter writer = new PrintWriter("resultado.doc", "UTF-8");
	            writer.println(data);
	            writer.close();             
				
	        } catch (Exception e) {
				e.printStackTrace();
	        }
	        
		}
		
		public void createTxt(String data) {
			try {
	            PrintWriter writer = new PrintWriter("resultado.txt", "UTF-8");
	            writer.println(data);
	            writer.close();             
				
	        } catch (Exception e) {
				e.printStackTrace();
	        }        
		}
		
		
		private class PredicateVar implements Predicate<VariablesDTO>{
	        private String var;
	        
			public PredicateVar(String var) {
				this.var = var;
			}
			
			@Override
			public boolean evaluate(VariablesDTO object) {
				return object.getNombre().equals(var);
			}
			
		}
	
}
